#include "appender_file_daily.h"

#include <vector>

#include "os.h"
#include "duration.h"

namespace afcore {
namespace log {

template<typename Mutex, typename FileNameCalc>
CAppenderFileDaily<Mutex, FileNameCalc>::CAppenderFileDaily(RFileName base_filename, int rotation_hour, int rotation_minute, bool truncate, uint16_t max_files)
  : base_filename_(std::move(base_filename))
  , rotation_h_(rotation_hour)
  , rotation_m_(rotation_minute)
  , truncate_(truncate)
  , max_files_(max_files)
  , filename_q_() {
  if (rotation_hour < 0 || rotation_hour > 23 || rotation_minute < 0 || rotation_minute > 59) {
    ThrowCLogeException("CAppenderFileDaily: Invalid rotation time in ctor");
  }

  auto now = RClockLog::now();
  auto filename = FileNameCalc::CalcFilename(base_filename_, NowTm(now));
  helper_file_.Open(filename, truncate_);
  rotation_tp_ = NextRotationTp();

  if (max_files_ > 0) {
    InitFileNameQueue();
  }
}

template<typename Mutex, typename FileNameCalc>
RFileName CAppenderFileDaily<Mutex, FileNameCalc>::FileName() {
  return helper_file_.FileName();
}

template<typename Mutex, typename FileNameCalc>
void CAppenderFileDaily<Mutex, FileNameCalc>::DoLog(const SLogMessage &msg) {
  auto time = msg.time;
  bool should_ratate = time >= rotation_tp_;
  if (should_ratate) {
    auto filename = FileNameCalc::CalcFilename(base_filename_, NowTm(time));
    helper_file_.Open(filename, truncate_);
    rotation_tp_ = NextRotationTp();
  }
  RMemoryBuf formatted;
  CAppenderBase<Mutex>::formatter_->Format(msg, formatted);
  helper_file_.Write(formatted);

  if (should_ratate && max_files_ > 0) {
    DeleteOld();
  }
}

template<typename Mutex, typename FileNameCalc>
void CAppenderFileDaily<Mutex, FileNameCalc>::DoFlush() {
  helper_file_.Flush();
}

template<typename Mutex, typename FileNameCalc>
void CAppenderFileDaily<Mutex, FileNameCalc>::InitFileNameQueue() {
  filename_q_ = CCircularQueue<RFileName>(static_cast<size_t>(max_files_));
  std::vector<RFileName> filenames;
  auto now = RClockLog::now();
  while (filenames.size() < max_files_) {
    auto filename = FileNameCalc::CalcFilename(base_filename_, NowTm(now));
    if (!PathExists(filename)) {
      break;
    }
    filenames.emplace_back(filename);
    now -= RHours(24);
  }
  for (auto iter = filenames.rbegin(); iter != filenames.rend(); ++iter) {
    filename_q_.EnQueue(std::move(*iter));
  }
}

template<typename Mutex, typename FileNameCalc>
tm CAppenderFileDaily<Mutex, FileNameCalc>::NowTm(RClockLog::time_point tp) {
  return fmt::localtime(RClockLog::to_time_t(tp));
}

template<typename Mutex, typename FileNameCalc>
RClockLog::time_point CAppenderFileDaily<Mutex, FileNameCalc>::NextRotationTp() {
  auto now = RClockLog::now();
  tm date = NowTm(now);
  date.tm_hour = rotation_h_;
  date.tm_min = rotation_m_;
  date.tm_sec = 0;
  auto rotation_time = RClockLog::from_time_t(std::mktime(&date));
  if (rotation_time > now) {
    return rotation_time;
  }
  return {rotation_time + RHours(24)};
}

template<typename Mutex, typename FileNameCalc>
void CAppenderFileDaily<Mutex, FileNameCalc>::DeleteOld() {
  RFileName current_file = FileName();
  if (filename_q_.Full()) {
    auto old_filename = std::move(filename_q_.Front());
    filename_q_.DeQueue();
    bool ok = 0 == RemoveIfExists(old_filename);
    if (!ok) {
      filename_q_.EnQueue(std::move(current_file));
      ThrowCLogeException("Failed removing daily file " + FileNameToString(old_filename), errno);
    }
  }
  filename_q_.EnQueue(std::move(current_file));
}

template class CAppenderFileDaily<std::mutex>;
template class CAppenderFileDaily<SNullMutex>;

} // !namespace log

using namespace log;

template<typename Factory>
std::shared_ptr<CLogger> FileDaily_MT(const std::string& logger_name, const RFileName& filename, int hour, int minute, bool truncate, uint16_t max_files) {
  return Factory::template Create<RAppenderFileDaily_MT>(logger_name, filename, hour, minute, truncate, max_files);
}

template<typename Factory>
std::shared_ptr<CLogger> FileDaily_ST(const std::string& logger_name, const RFileName& filename, int hour, int minute, bool truncate, uint16_t max_files) {
  return Factory::template Create<RAppenderFileDaily_ST>(logger_name, filename, hour, minute, truncate, max_files);
}

template AFCORE_COMMON_API std::shared_ptr<CLogger> FileDaily_MT(const std::string&, const RFileName&, int, int, bool, uint16_t);
template AFCORE_COMMON_API std::shared_ptr<CLogger> FileDaily_ST(const std::string&, const RFileName&, int, int, bool, uint16_t);

} // !namespace afcore